package org.budo.launcher.jetty;

import java.io.File;
import java.io.IOException;
import java.net.BindException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.budo.launcher.jetty.util.JettyLauncherUtil;
import org.budo.launcher.service.AbstractBudoLauncherService;
import org.budo.launcher.service.util.LauncherUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.slf4j.Slf4j;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.util.resource.JarResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.slf4j.Logger;

/**
 * @author lmw
 * @see org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory
 * @see https://github.com/nutzam/nutzboot/blob/dev/nutzboot-starter/nutzboot-starter-jetty/src/main/java/org/nutz/boot/starter/jetty/JettyStarter.java
 */
public class BudoJettyLauncher extends AbstractBudoLauncherService {
    private static final String WEB_SOCKET_SERVER_CONTAINER_INITIALIZER = "org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer";

    private static final Logger log = Slf4j.getLogger();

    private static final String PORT_KEY = "jetty.port";

    @Override
    public void run(String[] args) {
        Integer launcherPort = this.getLauncherPort(PORT_KEY);
        log.warn("#32 before run, port=" + launcherPort);

        try {
            this.startJettyServer(launcherPort);
        } catch (BindException e) {
            log.error("#44 startServer error, 端口绑定失败，退出程序，exit(1), launcherPort=" + launcherPort, e);
            System.exit(2); // 错误退出
        } catch (Throwable e) {
            log.error("#36 startServer error", e);
            System.exit(1); // 错误退出
        }

        log.warn("#44 after run, port=" + launcherPort);
    }

    private void startJettyServer(Integer launcherPort) throws Throwable {
        Server server = new Server(launcherPort);

        WebAppContext webAppContext = new WebAppContext();
        webAppContext.setDefaultsDescriptor("budo-jetty-defaults-descriptor.xml");

        final String contextPath = this.getContextPath();
        webAppContext.setContextPath(contextPath);

        ResourceCollection resourceCollection = this.baseResource();
        webAppContext.setBaseResource(resourceCollection);

        log.info("#79 webAppContext=" + webAppContext);

        if (null == contextPath || contextPath.isEmpty() || contextPath.equals("/")) {
            server.setHandler(webAppContext);
        } else {
            HandlerList handlerList = new HandlerList();
            handlerList.addHandler(webAppContext);
            handlerList.addHandler(new BudoIndexHandler(contextPath));
            server.setHandler(handlerList);
        }

        log.info("#59 server starting" //
                + ", server=" + server //
                + ", handlers=" + StringUtil.join(server.getHandlers())//
                + ", childHandlers=" + StringUtil.join(server.getChildHandlers())//
                + ", connectors=" + StringUtil.join(server.getConnectors())//
                + ", sessionIdManager=" + server.getSessionIdManager() //
                + ", threadPool=" + server.getThreadPool());

        this.initWebsocketContainer(webAppContext);

        server.start();
    }

    private void initWebsocketContainer(WebAppContext webAppContext) {
        // websocket init before start
        if (ReflectUtil.hasClass(WEB_SOCKET_SERVER_CONTAINER_INITIALIZER)) {
            log.warn("#78 websocket init before start, webAppContext=" + webAppContext);
            WebSocketServerContainerInitializer.configureContext(webAppContext);
        }
    }

    private ResourceCollection baseResource() throws IOException {
        File root = LauncherUtil.getDocumentRoot();
        log.info("#87 root=" + root);

        if (null == root) {
            root = LauncherUtil.createTempDir("jetty-temp", this.getLauncherPort(PORT_KEY));
        }

        List<Resource> resources = new ArrayList<Resource>();

        if (root.isDirectory()) { // 临时目录
            File rootCanonicalFile = root.getCanonicalFile();
            Resource rootCanonicalResource = Resource.newResource(rootCanonicalFile);
            resources.add(rootCanonicalResource);
        } else {
            Resource rootResource = Resource.newResource(root);
            Resource rootJarResource = JarResource.newJarResource(rootResource);
            resources.add(rootJarResource);
        }

        List<URL> urlsOfJarsWithMetaInfResources = JettyLauncherUtil.getUrlsOfJarsWithMetaInfResources();
        for (URL resourceJarUrl : urlsOfJarsWithMetaInfResources) {
            Resource resource = JettyLauncherUtil.createResource(resourceJarUrl);
            if (resource.exists() && resource.isDirectory()) {
                resources.add(resource);
            }
        }

        File budoWebAppDir = LauncherUtil.getBudoWebAppDir();
        if (null != budoWebAppDir) {
            Resource webAppResource = Resource.newResource(budoWebAppDir);
            resources.add(webAppResource);
        }

        // 本地开发环境只有一条 就是 src/main/webapp 目录
        Resource[] resourceArray = resources.toArray(new Resource[resources.size()]);
        ResourceCollection resourceCollection = new ResourceCollection(resourceArray);
        return resourceCollection;
    }
}